package moro;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

public abstract class Sensor extends Device {
	// JB: The fact that this code has a lot of comments with question marks in it (see below)
	// does not give a lot of confidence. I have chosen to let the comments in, as they could be
	// indicators where to look if things break down.
	
	int orientation = 1;      // 1: clockwise  -1: otherwise
	double rotStep = 1.0;     // one degree
	double numSteps = 0;
	double maxSteps = 0;
	
	// JB: The use of the booleans detect and scan (and Device.running) makes the code very complex
	// and easy to break. See executeCommand() and nextStep(). This could do with a decent refactoring!
	boolean detect = false;
	boolean scan = false;
	int range = 100;          // maximum range
	AudioInputStream ais;
	Clip clip;
	Measure detectMeasure = null;
	ArrayList<Measure> scanMeasures = new ArrayList<Measure>();

	public Sensor(String name, Robot robot, Position localPos, Environment environment) {
		super(name, robot, localPos, environment);
		
	    try {
	    	File file = new File("C:/sonar_ping.wav");
	    	URI uri = file.toURI();
	    	URL url = uri.toURL();
	    	System.out.println(url);
	    	clip = AudioSystem.getClip();
			ais = AudioSystem.getAudioInputStream(url);
			clip.open(ais);
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (LineUnavailableException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedAudioFileException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	//only difference between scanners: 
	public abstract double read(boolean first);
	@Override
	public void paint(Graphics g)
	{
		super.paint(g);	
	}


	
	public double pointToObstacle(Polygon polygon, Point2D centre, Point2D front, boolean first) {
		int j = 0;
		double minDistance = -1.0;
		double dist = -1.0;
		double px, py;
		double x1, y1, x2, y2;
		double m1, q1, m2, q2;
		Line2D.Double beam = new Line2D.Double(centre, front);

		for(int i=0; i < polygon.npoints; i++) {
			j = i+1;
			if(j == polygon.npoints)
				j = 0;
			x1 = polygon.xpoints[i];
			y1 = polygon.ypoints[i];
			x2 = polygon.xpoints[j];
			y2 = polygon.ypoints[j];
			if(beam.intersectsLine(x1, y1, x2, y2)) {
				// calculates the intersection point
				if(centre.getX() == front.getX()) {
					px = centre.getX();
					py = (y2 - y1) / (x2 - x1) * (px - x1) + y1;
				}
				else
					if(x1 == x2) {
						px = x1;
						py = (front.getY()-centre.getY()) / (front.getX()-centre.getX()) * (px - centre.getX()) + centre.getY();
					}
					else {
						m1 = (y2 - y1) / (x2 - x1);
						q1 = y1 - m1 * x1;
						m2 = (front.getY()-centre.getY()) / (front.getX()-centre.getX());
						q2 = centre.getY() - m2 * centre.getX();
						px = (q2 - q1) / (m1 - m2);
						py = m1 * px + q1;
					}
				// calculates the distance between (cx, cy) and the intersection point
				dist = Point2D.Double.distance(centre.getX(), centre.getY(), px, py);
				if(minDistance == -1.0 || minDistance > dist)
					minDistance = dist;
				if(first && minDistance > 0.0)
					return minDistance;
			}
		}
		return minDistance;
	}

	public void executeCommand(String command) {
		if(command.indexOf("ROTATETO") > -1) {
			rotStep = 4.0;
			double direction = Math.abs(Double.parseDouble(command.trim().substring(9).trim()));
			while(direction < 0.0)
				direction+=360.0;
			while(direction > 360.0)
				direction-=360.0;
			double dirDiff = direction - Math.toDegrees(localPos.getT());   // ??????????????
			if(dirDiff >= 0.0 && dirDiff <= 180.0) {
				numSteps = dirDiff / rotStep;
				maxSteps = numSteps;
				orientation = 1;
			}
			else if(dirDiff >= 0.0 && dirDiff > 180.0) {
				numSteps = (360.0 - dirDiff) / rotStep;
				maxSteps = numSteps;
				orientation = -1;
			}
			else if(dirDiff < 0.0 && -dirDiff <= 180.0) {
				numSteps = -dirDiff / rotStep;
				maxSteps = numSteps;
				orientation = -1;
			}
			else if(dirDiff < 0.0 && -dirDiff > 180.0) {
				numSteps = (360.0 + dirDiff) / rotStep;
				maxSteps = numSteps;
				orientation = 1;
			}
			running = true;
		}
		else if(command.equalsIgnoreCase("READ")) {
			writeOut("t=" + Double.toString(this.localPos.getT()) + " d=" + Double.toString(this.read(true)));
			// ??????????????
		}
		else if(command.equalsIgnoreCase("SCAN")) {
			try
			{	
				clip.setFramePosition(0);
			     clip.start();	     
			     
			}
			catch(Exception e)
			{
				System.out.println(e);
			}

			rotStep = 1.0;
			scanMeasures.clear();
			numSteps = 360.0 / rotStep;
			maxSteps = numSteps;
			orientation = 1;
			scan = true;
			// send the list of measures
			commands.add("GETMEASURES");
			running = true;
		}
		else if(command.equalsIgnoreCase("GETMEASURES")) {
			Measure measure = null;
			String measures = "SCAN";
			for(int i=0; i < scanMeasures.size(); i++) {
				measure = scanMeasures.get(i);
				measures += " d=" + measure.distance + " t=" + measure.direction;
			}
			writeOut(measures);
		}
		else if(command.equalsIgnoreCase("DETECT")) {
			detect = true;
			rotStep = 8.0;
			if(detectMeasure != null) {
				writeOut("LASER DETECT d=" + detectMeasure.distance + " t=" + detectMeasure.direction);
				detectMeasure = null;
			}
			else if(localPos.getT() == Math.toRadians(45.0)) {   // ?????????????
				// move the laser to the left position
				commands.add("ROTATETO 315");
				// repeats this command
				commands.add("DETECT");
			}
			else if(localPos.getT() == Math.toRadians(315.0)) {  // ??????????????
				// move the laser to the right position
				commands.add("ROTATETO 45");
				// repeats this command
				commands.add("DETECT");
			}
			else {
				// move the laser to the right position
				commands.add("ROTATETO 45");
				// repeats this command
				commands.add("DETECT");
			}
		}
		else
			writeOut("DECLINED");
	}


	public void nextStep() {
		if(running && numSteps > 0.0) {
			if(numSteps < 1.0)
				localPos.rototras(0.0, 0.0, orientation*numSteps*rotStep);
			else
				localPos.rototras(0.0, 0.0, orientation*rotStep);
			environment.repaint();
			numSteps-=1.0;
			running = true;
		}
		else if(running) {
			running = false;
			if(!detect && !scan)
				writeOut("LASER ARRIVED");
		}
		if(detect) {
			double distance = this.read(true);
			if(distance > -1.0) {
				if(detectMeasure == null)
					detectMeasure = new Measure(distance, localPos.getT());  // ?????????????
				else if(detectMeasure.distance > distance)
					detectMeasure.set(distance, localPos.getT());  // ????????????
			}
		}
		else if(scan) {
			double distance = this.read(false);
			if(distance > -1.0)
				scanMeasures.add(new Measure(distance, localPos.getT()));  // ??????????????
		}
	}

	// Innerclass Measure represents measurements from the Laser
	class Measure {
		public double distance = 0;
		public double direction = 0.0;
		Measure(double dist, double dir) {
			distance = dist;
			direction = dir;
			while(direction >= 2.0*Math.PI)
				direction -= 2.0*Math.PI;
			while(direction < 0.0)
				direction += 2.0*Math.PI;
		}
		public void set(double dist, double dir) {
			distance = dist;
			direction = dir;
			while(direction >= 2.0*Math.PI)
				direction -= 2.0*Math.PI;
			while(direction < 0.0)
				direction += 2.0*Math.PI;
		}
	}

}
